#include "lua_vm.h"

int32 err_callback(lua_State* L) {
	int topIndex = lua_gettop(L);
	const char *msg = lua_tostring(L, -1);
	if (msg) {
		luaL_traceback(L, L, msg, 1);
	}
	else {
		lua_pushliteral(L, "no message");
	}
	return 1;
}

LuaVm::~LuaVm() {
	close();
}

bool LuaVm::close() {
	if (L) {
		lua_close(L);
		L = NULL;
		return true;
	}
	return false;
}

bool LuaVm::load() {
	L = luaL_newstate();
	luaL_openlibs(L);
	loadGameLibs();
	lua_pushcfunction(L, err_callback);
	errHandle_ = lua_gettop(L);

	int ret = luaL_loadfile(L, entry_.c_str());
	if (ret == LUA_OK) {
		ret = lua_pcall(L, 0, 0, 0);
	}

	if (ret != LUA_OK) {
		int topIndex = lua_gettop(L);
		if (topIndex > 0) {
			const char *msg = lua_tostring(L, topIndex);
			if (msg) {
				printf("Error:%s\n", msg);
			}
			lua_pop(L, 1);
		}
	}

	return true;
}

void LuaVm::setParam(StreamReader& params) {
	uint8 type;
	params >> type;
	switch (type)
	{
	case MSGPARAM_TBOOL:
	{
		uint8 v;
		params >> v;
		lua_pushboolean(L, v == 1);
	}
	break;
	case MSGPARAM_TNUMBER:
	{
		float64 v;
		params >> v;
		lua_pushnumber(L, v);
	}
	break;
	case MSGPARAM_TINTEGER:
	{
		uint64 v;
		params >> v;
		lua_pushinteger(L, v);
	}
	break;
	case MSGPARAM_TSTRING:
	{
		const char* v;
		params >> v;
		lua_pushstring(L, v);
	}
	break;
	case MSGPARAM_TLIGHTUSERDATA:
	{
		void* v;
		params >> v;
		lua_pushlightuserdata(L, v);
	}
	break;
	case MSGPARAM_TTABLE:
	{
		lua_newtable(L);
		int32 sz;
		params >> sz;
		for (int32 i = 0; i < sz; i++) {
			setParam(params);
			setParam(params);
			lua_settable(L, -3);
		}
	}
	break;
	default:
	{
		lua_pushnil(L);
	}
		break;
	}
}

int32 LuaVm::setParams(StreamReader& params) {
	if (!params.readEnd()) {
		uint8 sz;
		params >> sz;
		for (uint8 i = 0; i < sz; i++) {
			setParam(params);
		}
		return sz;
	}
	return 0;
}

bool LuaVm::readRet(lua_State* L, int32 index, StreamWriter& rets) {
	int32 type = lua_type(L, index);
	switch (type)
	{
	case LUA_TBOOLEAN:
	{
		rets << (lua_toboolean(L, index) == 1);
	}
	break;
	case LUA_TNUMBER:
	{
		if (lua_isinteger(L, index)) {
			rets << (uint64)lua_tointeger(L, index);
		}
		else {
			rets << (float64)lua_tonumber(L, index);
		}
	}
	break;
	case LUA_TSTRING:
	{
		rets << lua_tostring(L, index);
	}
	break;
	case LUA_TLIGHTUSERDATA:
	{
		rets << lua_touserdata(L, index);
	}
	break;
	default:
	{
		printf("Error: LuaVm::readRet unsupported type = %d !\n", type);
		shengine_assert(0);
		return false;
	}
	break;
	}
	return true;
}

bool LuaVm::readRetEx(lua_State* L, int32 index, StreamWriter& writer) {
	int32 type = lua_type(L, index);
	switch (type)
	{
	case LUA_TBOOLEAN:
	{
		writer << (uint8)MSGPARAM_TBOOL;
		writer << (lua_toboolean(L, index) == 1);
	}
	break;
	case LUA_TNUMBER:
	{
		if (lua_isinteger(L, index)) {
			writer << (uint8)MSGPARAM_TINTEGER;
			writer << (uint64)lua_tointeger(L, index);
		}
		else {
			writer << (uint8)MSGPARAM_TNUMBER;
			writer << (float64)lua_tonumber(L, index);
		}
	}
	break;
	case LUA_TSTRING:
	{
		writer << (uint8)MSGPARAM_TSTRING;
		writer << lua_tostring(L, index);
	}
	break;
	case LUA_TLIGHTUSERDATA:
	{
		writer << (uint8)MSGPARAM_TLIGHTUSERDATA;
		writer << lua_touserdata(L, index);
	}
	break;
	case LUA_TTABLE:
	{
		writer << (uint8)MSGPARAM_TTABLE;
		int32 offset = writer.length();
		int32 sz = 0;
		writer << sz;
		lua_pushnil(L);
		while (lua_next(L, index)) {
			readRetEx(L, -2, writer);
			readRetEx(L, -1, writer);
			sz++;
			lua_pop(L, 1);
		}
		*(int32*)(writer.data() + offset) = sz;
	}
	break;
	default:
	{
		writer << (uint8)MSGPARAM_TINVALID;
	}
	break;
	}
	return true;
}

bool LuaVm::_call(const char* entryfunc, const char* modname, uint64 handle, const char* f, StreamReader* params, StreamWriter* rets, ReadRetFunc readf) {
	bool callModuleFunc = entryfunc && modname;
	const char* callF = callModuleFunc ? entryfunc : f;
	int oldTopIndex = lua_gettop(L);
	int32 ftype = lua_getglobal(L, callF);
	if (ftype != LUA_TFUNCTION) {
		lua_pop(L, 1);
		printf("Error: Function[%s] not exists !\n", f);
		return false;
	}
	int32 psz = 0;
	if (entryfunc && modname) {
		lua_pushstring(L, modname);
		lua_pushstring(L, f);
		lua_pushinteger(L, handle);
		psz += 3;
	}
	psz += params ? setParams(*params) : 0;
	int32 ret = lua_pcall(L, psz, LUA_MULTRET, errHandle_);
	int topIndex = lua_gettop(L);
	if (ret != LUA_OK) {
		const char* msg = lua_tostring(L, topIndex);
		printf("Error: %s\n", msg);
		if (topIndex > oldTopIndex) {
			lua_pop(L, topIndex - oldTopIndex);
		}
		return false;
	}
	if (topIndex > oldTopIndex) {
		if (rets) {
			(*rets) << (uint8)(topIndex - oldTopIndex);
			for (int32 i = oldTopIndex + 1; i <= topIndex; i++) {
				if (!(*readf)(L, i, *rets)) {
					return false;
				}
			}
		}
		lua_pop(L, topIndex - oldTopIndex);
	}
	return true;
}

bool LuaVm::call(const char* f, StreamReader* params, StreamWriter* rets) {
	check_str(f);
	return _call(NULL, NULL, -1, f, params, rets, &LuaVm::readRet);
}

bool LuaVm::callEx(const char* f, StreamReader* params, StreamWriter* rets) {
	check_str(f);
	return _call(NULL, NULL, -1, f, params, rets, &LuaVm::readRetEx);
}

bool LuaVm::callModule(const char* entryfunc, const char* modname, uint64 handle, const char* f, StreamReader* params, StreamWriter* rets) {
	shengine_assert(entryfunc && modname);
	check_str(f);
	return _call(entryfunc, modname, handle, f, params, rets, &LuaVm::readRet);
}

bool LuaVm::callModuleEx(const char* entryfunc, const char* modname, uint64 handle, const char* f, StreamReader* params, StreamWriter* rets) {
	shengine_assert(entryfunc && modname);
	check_str(f);
	return _call(entryfunc, modname, handle, f, params, rets, &LuaVm::readRetEx);
}
